﻿using System.Linq;
using Jsdl.Domain.Input.JsonService;
using Jsdl.Domain.Intermediate;
using Jsdl.Domain.Type;

namespace Jsdl.Generators
{
    public interface IClassGenerator
    {
        string GeneratePropertyCode(string propertyName, string propertyOutputTypeName, JsdlProperty property);
        string GenerateClassCode(string outputTypeName, string outputTypeBody);
        string GetTypeName(JsdlType type);
        string GetArrayTypeName(string listType, string itemType);
        string GetTypeName(string className);
    }

    public interface IJsonServiceClientGenerator
    {
        string GenerateJsonServiceCode();
        string GenerateJsonServiceClassCode();
        string GenerateJsonServiceOperationCode(string operationName, JsdlOperation operation);
    }
    
    public class CsharpCodeGenerator : CodeGenerator, IClassGenerator, IJsonServiceClientGenerator
    {
		public bool UseMyToolkit { get; set; }

        public override string GenerateJsonServiceCode()
		{
		    var httpUsings = UseMyToolkit
		        ? "using MyToolkit.Networking;\nusing System.Text;\n"
		        : "using System.Net.Http;\n";

            return LoadTemplate("File")
                .Replace("[usings]", httpUsings)
				.Replace("[namespace]", NamespaceName); 
		}

		public override string GenerateJsonServiceClassCode()
		{
			return LoadTemplate("ServiceClient")
				.Replace("[types]", Classes.Count > 0 ? "typeof(" + string.Join("), typeof(", Classes.Select(c => c.Name)) + ")" : "")
				.Replace("[initializeRequest]", UseMyToolkit ? 
					"partial void PrepareRequest(HttpGetRequest request);\n" +
					"partial void ProcessResponse(HttpGetRequest request, HttpResponse response);" : 
					"partial void PrepareRequest(HttpClient request);\n" +
					"partial void ProcessResponse(HttpClient request, HttpResponseMessage response);")
				.Replace("[name]", ClientClassName);
		}

		public override string GenerateJsonServiceOperationCode(string operationName, JsdlOperation operation)
		{
			var jsonInput = 
				operation.Transport == JsdlOperationTransport.post && 
				operation.Envelope == JsdlOperationEnvelope.json; 

			var postVariable = "";
			operationName = ConvertName(operationName);

			var returnType = "Task";
			var returnCode = "";

			// parameters
			var parameters = "";
			var parametersCode = "";

			if (jsonInput)
				parametersCode = "var parameters = new List<object>();\n";

			if (operation.Transport != JsdlOperationTransport.get)
				postVariable = "JsonConvert.SerializeObject(parameters)";

			var args = "";
			if (operation.Parameters != null) // POST / GET
			{
				foreach (var param in operation.Parameters)
				{
					var name = ConvertName(param.Name, true);
					parameters += GenerateType(param) + " _" + name + ", ";

					if (jsonInput)
						parametersCode += "parameters.Add(_" + name + ");\n";
					else if (operation.Envelope == JsdlOperationEnvelope.url)
						args += ", Uri.EscapeUriString(_" + name + ".ToString())";
					else // default => query
						parametersCode += "if (!object.Equals(_" + name + ", null)) { url += \"" +
							name + "=\" + Uri.EscapeUriString(\"\" + _" + name + ") + \"&\"; } \n";
				}
			}

			if (operation.Parameter != null) // POST only
			{
				if (jsonInput)
					parametersCode += "parameters.Add(_parameter);\n";
				else
					postVariable = "_parameter.ToJson()";
				parameters += GenerateType(operation.Parameter, operationName + "Request") + " _parameter, ";
			}

			parameters = parameters.Trim(' ').Trim(',');
			parametersCode = parametersCode.Trim('\n');

			// return value
			if (operation.Returns != null)
			{
				var isPrimitiveType = false;
				var type = GenerateType(operation.Returns, operationName + "Response", out isPrimitiveType);

				returnCode = "return JsonConvert.DeserializeObject<" + type + ">(data);";
				returnType = "Task<" + type + ">";
			}

		    var target = !string.IsNullOrEmpty(args)
		        ? @""" + string.Format(""" + operation.Target + @"""" + args + @") + """
		        : operation.Target;

            var throwExceptionTemplate = LoadTemplate("ServiceOperationException");
			var getOperationTemplate = LoadTemplate(!UseMyToolkit ? "ServiceOperationGet" : "ServiceOperationGetMyToolkit");
            var postOperationTemplate = LoadTemplate(!UseMyToolkit ? "ServiceOperationPost" : "ServiceOperationPostMyToolkit");

			return (operation.Transport == JsdlOperationTransport.get ? getOperationTemplate : postOperationTemplate)
				.Replace("[returnType]", returnType)
				.Replace("[throwException]", throwExceptionTemplate)
				.Replace("[name]", operationName)
				.Replace("[delim]", operation.Target.Contains("?") ? "&" : "?")
                .Replace("[target]", target)
				.Replace("[postVariable]", postVariable)
				.Replace("[parameters]", parameters)
				.Replace("[parametersBody]", IntendCode(parametersCode))
				.Replace("[returnCode]", returnCode);
		}
        
		public override string GeneratePropertyCode(string propertyName, string targetPropertyType, JsdlProperty property)
		{
			return LoadTemplate("Property")
				.Replace("[type]", targetPropertyType)
				.Replace("[memberName]", ConvertName(propertyName, true))
				.Replace("[jsonName]", propertyName)
				.Replace("[required]", property.Required ? "Required.Always" : "Required.Default")
				.Replace("[propertyName]", ConvertName(propertyName));
		}

		public override string GenerateClassCode(string outputTypeName, string outputTypeBody)
		{
			return LoadTemplate("Class")
				.Replace("[name]", outputTypeName)
				.Replace("[body]", outputTypeBody);
		}

        public override string GetTypeName(string className)
        {
            return ConvertName(className);
        }
        
        public override string GetTypeName(JsdlType type)
		{
            if (type is JsdlProperty)
            {
                var property = (JsdlProperty)type;

                var postfix = "";
                if (!property.Required && property.Type != "array" && property.Type != "dictionary" && property.Type != "object" && property.Type != "string")
                    postfix = "?";

                if (property.Type == "number")
                    return "decimal" + postfix;
                if (property.Type == "integer")
                    return "long" + postfix;
                if (property.Type == "string" && property.Format == "date-time")
                    return "DateTime" + (!property.Required ? "?" : "");
            }
            else
            {
                if (type.Type == "number")
                    return "decimal";
                if (type.Type == "integer")
                    return "long";
            }

			return GetTypeName(type.Type);
		}

		public override string GetArrayTypeName(string listType, string itemType)
		{
			if (listType == "array")
				return "ObservableCollection<" + itemType + ">";
			else
				return "Dictionary<string, " + itemType + ">";
		}
	}
}
